import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: wx
 * Date: 2024-03-17
 * Time: 16:11
 */
public class LongConTcpserver {
    private ServerSocket serverSocket= null;

    public LongConTcpserver(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }


    /**
     * 使用多线程处理
     * @startThraed
     */
   /* public void start() throws IOException {
        System.out.println("服务器启动!");
        while(true) {
            //通过accept来"接听电话"
            Socket clientSocket = serverSocket.accept();
            Thread t1 = new Thread(() -> {
                try {
                    processConnection(clientSocket);//此时,把 processConnection 操作交给新的线程来负责了
                    //                                主循环就会快速执行完一次,回到accept这里阻塞等待新的客户的到来
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            });
            t1.start();
        }
    }*/


    /**
     * 使用线程池处理
     * @startThreadpool
     */
    public void start() throws IOException {
        System.out.println("服务器启动!");
        ExecutorService pool = Executors.newCachedThreadPool();
        while(true) {
            Socket clientSocket = serverSocket.accept();
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        processConnection(clientSocket);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
        }
    }

    // 1.通过这个方法来处理一次连接,连接建立的过程中就会涉及到多次请求交互响应
    private void processConnection(Socket clientSocket) throws IOException {
        System.out.printf("[%s:%d] 客户端上线 \n", clientSocket.getInetAddress(), clientSocket.getPort());
        //循环读取客户端请求并响应/
        try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream() ) {
            Scanner scanner = new Scanner(inputStream);
            while(true){
                //通过 InputStream 读取数据
               /* byte[] buffer = new byte[1024];
                inputStream.read(buffer); 此处完全可以借助read来完成,read是把收到的数据放到 byte 数组中了
                                            后续根据请求处理响应还要把这个byte 数组转成String
                                            也可以使用更简单的办法完成,如上使用Scanner:*/
                if (!scanner.hasNext()) {
                    //读取完毕,客户端断开连接,就会产生读取完毕
                    System.out.printf("[%s:%d]  客户端下线!\n",clientSocket.getInetAddress(),clientSocket.getPort());
                    break;
                }
                // 1.读取请求并解析,这里注意隐藏的约定. next 读取的时候要读到空白符才会结束
                //   因此就要求客户端发来的请求必须带有空白符结尾,比如带有 \n(换行)/空格
                String request = scanner.next();
                // 2.根据请求计算响应
                String response = process(request);
                // 3.把响应给客户端
                /*通过这种方式可以写会,但是这种方法不方便给返回的响应中添加 \n
                outputStream.write(response.getBytes(), 0, response.getBytes().length);*/
                //也可以给 outputStream 套上一层,完成更方便的写入
                PrintWriter printWriter = new PrintWriter(outputStream);
                printWriter.println(response);
                printWriter.flush();


                System.out.printf("[%s:%d] req: %s, resp: %s\n", clientSocket.getInetAddress(),clientSocket.getPort(),
                        request,response);

            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
                clientSocket.close();
        }

    }

    private String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        LongConTcpserver server = new LongConTcpserver(9090);
        server.start();
    }
}
/**
 * 长连接,短连接
 *  短连接 -->一对一
 *  长连接 -->一对多 (核心思路,使用多线程)
 *   单个线程,无法既能给客户提供循环服务,又能去快速调用到第二次 accept
 *    简单的办法就是引入多线程.
 *    主线程负责执行 accept.每次有一个客户过来连上来就分配一个新的线程,由新的线程给客户端提供服务
 *  上述问题只是代码的区别问题,不是TCP引起的,主要原因在于两层循环嵌套引起
 *  UDP服务器,只有一层循环,就不涉及这样的问题,之前UDP服务器天然就可以处理多个客户端的请求
 *
 */



     /**
     * 每来一个客户端,就会创建一个新的线程
      * 每次客户端结束,就要销毁这个线程
      * 如果客户端比较多,就会使服务器 频繁创建销毁 线程
      * 使用线程池解决
     * @throws IOException
     */
/*
    public void start() throws IOException {
        System.out.println("服务器启动!");
        while(true) {
            //通过accept来"接听电话"
            Socket clientSocket = serverSocket.accept();
            Thread t1 = new Thread(() -> {
                try {
                    processConnection(clientSocket);//此时,把 processConnection 操作交给新的线程来负责了
                    //                                主循环就会快速执行完一次,回到accept这里阻塞等待新的客户的到来
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            });
            t1.start();
        }
    }
*/



/**
 * 不是每次创建线程了,而是把创建好的线程放在线程池里,以备下次使用
 * 这样就可以减少线程创建销毁的问题了
 * 线程池解决的事线程 频繁创建销毁 的问题
 * -------------------------------------------
 * 要是有线程频繁创建,但是不销毁的问题呢?
 * 客户端处理问题很短(网站)
 * 每个客户端处理过程都很长(游戏服务器)
 * 如果继续使用线程池/多线程,此时就会导致当前的服务器上一下积累了大量的线程
 * 此时对于服务器的负担会非常重,引起调度成本的增加
 * 为了解决上述问题,还可以引入其他方案
 * 1.协程(轻量级线程)
 *   本质上还是一个线程,用户多台可以通过手动调度的方式让这个线程 "并发" 的做多任务
 *                                省去系统调度的开销了
 *
 * 2. IO 多路复用(一个线程被复用处理多个socket)
 * 系统内核级别的机制
 * 本质上是让一个线程同时区处理多个socket
 * 本质在于这些 socket 数据,并非统一时刻需要处理
 * 举个栗子,比如:
 * 晚上吃啥?
 * A 想吃肉夹馍
 * B 想吃熏肉大饼
 * C 想吃饺子
 * 咋办?
 * 我们去一个小吃街,各式各样的饭都有,都能买到
 * 我们只需要区去小摊买
 * 方案一: 一个人买(单线程)
 * 先买A 再买B 再买C,齐活了.
 * 这个方案效率低
 * 方案二: 我们三个人一起出动 (多线程)
 * 每个人负责买自己的
 * 效率提高了,系统开销就会变大
 * 方案三:还是我一个人买 (IO多路复用{基本盘在于,虽然有多个socket只是少数[需要读写数据的socket],大部分都是在等.使用一个线程来等多个socket},
 *                      IO多路复用属于系统提供的机制,也有对应的API,但是用起来挺复杂的,
 *                      NIO => java对于IO多路复用API的封装
 *                      Epoll C++方向的重点内容)
 * 先全部点单,站在中间等(花一份时间,等三个任务)
 * 上述那个小摊好了,我就去哪里取
 * 效率也不低,系统开销也不高
 * 上述介绍的技术机制,都是软件上解决问题的方案
 * @throws IOException
 */
/*
     public void start() throws IOException {
         System.out.println("服务器启动!");
         ExecutorService pool = Executors.newCachedThreadPool();
         while(true) {
             //通过accept来"接听电话"
             Socket clientSocket = serverSocket.accept();

             pool.submit(new Runnable() {
                 @Override
                 public void run() {
                     try {
                         processConnection(clientSocket);
                     } catch (IOException e) {
                         throw new RuntimeException(e);
                     }
                 }
             });
         }
     }
*/
